{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Installing and importing Python packages in the Pyodide kernel\n",
    "\n",
    "The default Python kernel for JupyterLite powered by [pyodide](https://pyodide.org),\n",
    "supports installing packages from a number of sources:\n",
    "\n",
    "- packages distributed with `pyodide`\n",
    "  - these do not need to be explicitly installed\n",
    "- packages and shims distributed along with the kernel\n",
    "  - this includes some compatibility shims for the Jupyter stack\n",
    "- custom packages deployed with a JupyterLite site\n",
    "- pure python wheel packages on PyPI\n",
    "  - ending in _exactly_ `py3-none-any.whl`\n",
    "- or specially compiled wheels for the _exactly_ the version of pyodide and emscripten\n",
    "  installed\n",
    "  - ending in e.g. `cp310-cp310-emscripten_3_1_14_wasm32.whl`"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Recommended: `%pip install -q`\n",
    "\n",
    "The **recommended** way to install packages is the `%pip` magic:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -q \"traitlets >=5\" ipython"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{hint}\n",
    "- `%pip` helps keep your notebooks **portable** between different IPython runtimes.\n",
    "- `-q` helps keep this \"setup\" step reasonable when run under `ipykernel`\n",
    "```\n",
    "\n",
    "`%pip` supports a number of additional options:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip --help"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `--requirements`\n",
    "\n",
    "In addition to (or instead of) package names, any number of `--requirements` (or\n",
    "shorthand `-r`) may be given, pointing to [requirements files][reqs-txt].\n",
    "\n",
    "[reqs-txt]: https://pip.pypa.io/en/stable/reference/requirements-file-format\n",
    "\n",
    "These can be relative or absolute (though this can be tricky to predict)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -r data/requirements.txt -r ../data/more-requirements.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{hint}\n",
    "Using a `requirements.txt` is a good way to separate your content from your environment,\n",
    "and could be combined with other techniques to make even more portable notebooks.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `requirement.txt` lines\n",
    "\n",
    "A number of simple types of requirement specifications are\n",
    "[supported](./data/requirements.txt), including:\n",
    "\n",
    "- package names\n",
    "- package names with version specifiers supported by `micropip`\n",
    "- URLs of `.whl` archives\n",
    "- `-r` to _more_ requirements files\n",
    "  - relative to the _requiring_ file, not the `%pip` working directory"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some known **unsupported** features:\n",
    "\n",
    "- `.` or other local in-development paths\n",
    "  - but you _could_\n",
    "    - `%pip install` a build tool (e.g. `flit`)\n",
    "      - use its Python API\n",
    "        - install the resulting `.whl`\n",
    "- `--editable` (or `-e.`) local or remote paths\n",
    "- any version control system (VCS) paths\n",
    "- non-`.whl` URLs or local archives\n",
    "- `--constraint` (or `-c`) constraint files"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dealing with (missing) dependencies\n",
    "\n",
    "Even if a PyPI package _would be_ installable in the Pyodide kernel, sometimes its\n",
    "dependencies won't be."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    %pip install jupyter_server\n",
    "except Exception as err:\n",
    "    print(err)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `--verbose`\n",
    "\n",
    "As the real `pip` doesn't have an equivalent, `%pip` in the Pyodide kernel maps the\n",
    "`--verbose` flag to `keep_going`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    %pip install --verbose jupyter_server\n",
    "except Exception as err:\n",
    "    print(err)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{warning}\n",
    "Leaving this on for real `pip` generates a **lot** of output!\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `--no-deps`\n",
    "\n",
    "If some missing dependencies don't bother you, you can forge ahead without _any_\n",
    "dependencies with the `--no-deps` flag."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install jupyter_server --no-deps\n",
    "import jupyter_server\n",
    "\n",
    "jupyter_server.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While importable, it won't have all of its features, and may require special approaches\n",
    "to access features."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    import jupyter_server.services.contents.filemanager\n",
    "except Exception as err:\n",
    "    print(err)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Going down this road can be long, depending on how much you really need a particular\n",
    "function."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Hard Way\n",
    "\n",
    "The `piplite` package is importable, and can be used directly."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Importing `piplite`\n",
    "\n",
    "`piplite` needs to be imported before it is used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import piplite"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This package **won't** be installable in a \"traditional\" IPython installation, so you\n",
    "can gate it with an import check:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    import piplite\n",
    "except ImportError:\n",
    "    piplite = None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `piplite.install`\n",
    "\n",
    "> `piplite.install` is a wrapper around\n",
    "> [`micropip.install`](https://pyodide.org/en/stable/usage/loading-packages.html#micropip),\n",
    "> and offers more browser-focused options than `%pip`\n",
    "\n",
    "```{warning}\n",
    "Due to browser limitations, `piplite.install` is an _asynchronous` function, so it must be `await`ed.\n",
    "```\n",
    "\n",
    "`piplite.install` supports either a single package:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "await piplite.install(\"traitlets\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "or a list of packages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "await piplite.install([\"traitlets\", \"IPython\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It also has many additional options:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "?piplite.install"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (Pyodide)",
   "language": "python",
   "name": "python"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "python",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
